require 'test/unit'
require 'socket'
require 'timeout'
require 'lib/smppclient/net/tcp_connection'
require 'lib/smppclient/net/connection_closed_error'
require 'lib/smppclient/pdu/bind_transceiver'
require 'lib/smppclient/pdu/bind_transceiver_resp'
require 'lib/smppclient/pdu/unbind'
require 'lib/smppclient/pdu/unbind_resp'
require 'lib/smppclient/pdu/pdu_error'

class TestTcpConnection < MiniTest::Unit::TestCase

  include SmppClient::Net
  include SmppClient::Pdu

  HOST = 'localhost'
  PORT = 50000

  def test_open_close
    gs = GoodServer.new(HOST, PORT)
    gs.start

    conn = TcpConnection.new(HOST, PORT)
    conn.open
    conn.close

    gs.stop
  end

  def test_write_read
    gs = GoodServer.new(HOST, PORT)
    gs.start
    conn = TcpConnection.new(HOST, PORT)
    conn.open

    # bind
    conn.write(BindTransceiver.new)
    # bind response
    response = nil
    while response == nil
      response = conn.read
    end
    assert_equal(BindTransceiverResp.new.buffer.bytes, response.buffer.bytes)
    # unbind
    conn.write(Unbind.new)
    # unbind response
    response = nil
    while response == nil
      response = conn.read
    end
    assert_equal(UnbindResp.new.buffer.bytes, response.buffer.bytes)

    conn.close
    gs.stop
  end

  def test_read_before_open
    gs = GoodServer.new(HOST, PORT)
    gs.start

    conn = TcpConnection.new(HOST, PORT)
    assert_raises(ConnectionClosedError) { conn.read }

    gs.stop
  end

  def test_read_after_closed
    gs = GoodServer.new(HOST, PORT)
    gs.start

    conn = TcpConnection.new(HOST, PORT)
    conn.open
    conn.close
    assert_raises(ConnectionClosedError) { conn.read }

    gs.stop
  end

  def test_write_before_open
    gs = GoodServer.new(HOST, PORT)
    gs.start

    conn = TcpConnection.new(HOST, PORT)
    assert_raises(ConnectionClosedError) { conn.write(BindTransceiver.new) }

    gs.stop
  end

  def test_write_after_close
    gs = GoodServer.new(HOST, PORT)
    gs.start

    conn = TcpConnection.new(HOST, PORT)
    conn.open
    conn.close
    assert_raises(ConnectionClosedError) { conn.write(BindTransceiver.new) }

    gs.stop
  end

  def test_read_buffer_overfilled
    bs = BadServer.new(HOST, PORT)
    bs.start

    conn = TcpConnection.new(HOST, PORT)
    conn.open
    assert_raises(PduError) {
      response = nil
      while response == nil
        response = conn.read
      end
    }
    
    bs.stop
  end

  def test_read_pdu_by_parts
    bs = PartialServer.new(HOST, PORT)
    bs.start

    conn = TcpConnection.new(HOST, PORT)
    conn.open
    response = nil
    while response == nil
      response = conn.read
    end
    assert_equal(BindTransceiver.new.buffer.bytes, response.buffer.bytes)
    conn.close

    bs.stop
  end

end

class GoodServer < MiniTest::Unit::TestCase

  include SmppClient::Pdu

  def initialize(host, port)
    @host = host
    @port = port
  end

  def start
    @server_thread = Thread.new do
      begin
        @server = TCPServer.new(@host, @port)
        while (session = @server.accept)
          request = read(session)
          assert_equal(BindTransceiver.new.buffer.bytes, request)
          session.write(BindTransceiverResp.new.buffer.bytes.pack('c*'))

          request = read(session)
          assert_equal(Unbind.new.buffer.bytes, request)
          session.write(UnbindResp.new.buffer.bytes.pack('c*'))
        end
      rescue
        puts $!
        puts $@
      end
    end
    sleep 1
  end

  def read(socket)
    while (bytes = socket.recv(1000))
      if bytes.size != 0
        return bytes.unpack('c*')
      end
    end
  end

  def stop
    if @server_thread != nil
      @server.close
      @server_thread.kill
    end
  end

end

class BadServer < MiniTest::Unit::TestCase

  include SmppClient::Pdu

  def initialize(host, port)
    @host = host
    @port = port
  end

  def start
    @server_thread = Thread.new do
      begin
        @server = TCPServer.new(@host, @port)
        while (session = @server.accept)
          array = [0, 0, 255, 255].fill(0, 4, 1001)
          session.write(array.pack('c*'))
        end
      rescue
        puts $!
        puts $@
      end
    end
    sleep 1
  end

  def read(socket)
    while (bytes = socket.recv(1000))
      if bytes.size != 0
        return bytes.unpack('c*')
      end
    end
  end

  def stop
    if @server_thread != nil
      @server.close
      @server_thread.kill
    end
  end

end

class PartialServer < MiniTest::Unit::TestCase

  include SmppClient::Pdu

  def initialize(host, port)
    @host = host
    @port = port
  end

  def start
    @server_thread = Thread.new do
      begin
        @server = TCPServer.new(@host, @port)
        while (session = @server.accept)
          pdu = BindTransceiver.new.buffer.bytes
          half = pdu.size / 2
          part1 = pdu[0...half]
          part2 = pdu[half...pdu.size]
          session.write(part1.pack('c*'))
          sleep 1
          session.write(part2.pack('c*'))
        end
      rescue
        puts $!
        puts $@
      end
    end
    sleep 1
  end

  def read(socket)
    while (bytes = socket.recv(1000))
      if bytes.size != 0
        return bytes.unpack('c*')
      end
    end
  end

  def stop
    if @server_thread != nil
      @server.close
      @server_thread.kill
    end
  end

end